/*
    epoll模型实现网络通讯服务端
*/

#include <iostream>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/fcntl.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <netinet/tcp.h>
#include "InetAddress.h"


int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cout << "Usgae:./tcpepoll ip port" <<std::endl;
        return -1;
    }
    
    // 创建服务端用于监听的listen_fd,非阻塞的
    int listen_fd = socket(AF_INET, SOCK_STREAM|SOCK_NONBLOCK, IPPROTO_TCP);
    if (listen_fd < 0)
    {
        std::cout << "socket() failed" <<std::endl;
        return -1;
    }
    
    // 设置listen_fd的属性
    int opt = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, static_cast<socklen_t>(sizeof opt));  // 地址重用
    setsockopt(listen_fd, SOL_SOCKET, TCP_NODELAY, &opt, static_cast<socklen_t>(sizeof opt));   // 禁用Nagle算法，提高TCP连接的实时性
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &opt, static_cast<socklen_t>(sizeof opt));  // 端口重用
    setsockopt(listen_fd, SOL_SOCKET, SO_KEEPALIVE, &opt, static_cast<socklen_t>(sizeof opt));  // 开启TCP的心跳检测

    // struct sockaddr_in server_addr;
    // server_addr.sin_family = AF_INET;
    // server_addr.sin_addr.s_addr = inet_addr(argv[1]);
    // server_addr.sin_port = htons(std::atoi(argv[2]));
    InetAddress server_addr(argv[1], std::atoi(argv[2]));

    if (bind(listen_fd, server_addr.addr(), sizeof(server_addr)) < 0)
    {
        perror("bind() failed");
        return -1;
    }
    
    if (listen(listen_fd, 128) != 0)   // 高并发的网络服务器中，第二个参数需要大一点
    {
        perror("listen() failed");
        return -1;
    }
    
    // 创建epoll句柄(红黑树)
    int epoll_fd = epoll_create(1);
    // 为服务端的listen_sock准备读事件
    epoll_event ev;  // 事件的数据结构
    ev.data.fd = listen_fd;  // 指定事件的自定义数据，回随着epoll_wait()返回的事件一并返回
    ev.events = EPOLLIN;  // 打算让epollo监听listen_sock的读事件

    // 把需要监听的socket加入到epoll_fd中
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev);

    // 存放epoll返回的事件
    epoll_event evs[10];

    while (true)
    {
        // 等待监听的socket有事件发生  10表示evs的长度是10,10000表示超时时间为10s,-1表示不超时
        int infds = epoll_wait(epoll_fd, evs, 10, -1);
        // int infds = epoll_wait(epoll_fd, evs, 10, 10000);

        if (infds < 0)
        {
            // 调用epoll_wait失败
            perror("epoll_wait() failed");
            break;
        }
        
        if (infds == 0)
        {
            // epoll_wait() 超时
            perror("epoll_wait() timeout");
            continue;
        }
        
        // 如果infds>0 表示有事件发生，infds存放了发生事件的个数
        for (int i = 0; i < infds; i++)
        {   
            // 如果是客户端连接的socket有事件，表示接受缓存中有数据可以读(对端发送的报文已到达)，或者有客户端已断开连接
            if (evs[i].events & EPOLLRDHUP)
            {
                // 对方已关闭，有些系统检测不到，使用EPOLLIN，recv()返回0
                std::cout << "client(eventfd=" << evs[i].data.fd << ") disconnected" << std::endl;
                close(evs[i].data.fd);
            }                      // 普通数据|带外数据
            else if (evs[i].events & (EPOLLIN|EPOLLPRI))
            {
                if (evs[i].data.fd == listen_fd)
                {
                    // 如果发生事件的是listen_fd 表示已连接队列中有已准备好的socket(有新的客户端连上来了)
                    struct sockaddr_in client;
                    socklen_t len = sizeof(client);
                    int client_sock = accept4(listen_fd, (struct sockaddr *)&client, &len, SOCK_NONBLOCK); // 客户端连接的fd设置为非阻塞的·
                    if (client_sock < 0)
                    {
                        perror("accept failed");
                        continue;
                    }
                    InetAddress client_addr(client);
                    std::cout << "accept client(socket=" << client_sock << ")||ip=" << client_addr.ip() << "||port=" << client_addr.port() <<std::endl;

                    // 为新客户转呗可读事件，并添加到epoll中
                    ev.data.fd = client_sock;
                    ev.events = EPOLLIN|EPOLLET;  // 边缘触发
                    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_sock, &ev);
                }
                else
                {
                    // 接受缓冲区中有数据可以读取
                    char buffer[1024];
                    while (true)
                    {   // 使用非阻塞的IO，一次读取buffer大小的数据，直到全部的数据读取完毕
                        bzero(&buffer, sizeof(buffer));
                        ssize_t nread = read(evs[i].data.fd, buffer, sizeof(buffer));
                        if (nread > 0)
                        {
                            // 读取到了数据
                            std::cout << "recv(eventfd=" << evs[i].data.fd << "):" << buffer << std::endl;
                            // 将读取到的报文内容原封不动发回去
                            send(evs[i].data.fd, buffer, strlen(buffer), 0);
                        }
                        else if (nread == -1 && errno == EINTR)
                        {
                            // 读取数据的时候被信号中断，继续读取
                            continue;
                        }
                        else if (nread == -1 && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))
                        {
                            // 全部的数据已读取完毕
                            break;
                        }
                        else if (nread == 0)
                        {
                            // 客户端连接已断开
                            std::cout << "client(eventfd=" << evs[i].data.fd << ") disconnected" << std::endl;
                            close(evs[i].data.fd);
                            break;
                        }
                    }
                }
            }
            else if (evs[i].events & EPOLLOUT)
            {
                // 有数据要写  
            }
            else
            {
                // 其他事件，都视为错误
                std::cout << "client(eventfd=" << evs[i].data.fd << ") error" << std::endl;
                close(evs[i].data.fd);
            }
        }
    }
    return 0;
}

